/**
 * Copyright (c) 2011 committers of YAKINDU and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     committers of YAKINDU - initial API and implementation
 */
package org.yakindu.sct.generator.builder;

import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.preference.IPreferenceStore;
import org.yakindu.sct.generator.builder.resources.DefaultResourceBlacklist;
import org.yakindu.sct.generator.core.GeneratorActivator;
import org.yakindu.sct.model.sgen.GeneratorEntry;
import org.yakindu.sct.model.sgen.GeneratorModel;
import org.yakindu.sct.model.sgraph.Statechart;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

/**
 * 
 * @author andreas muelder - Initial contribution and API
 * 
 */
public class SCTBuilder extends IncrementalProjectBuilder {

	// TODO Remove dependency to fileextension and read referenced elements from
	// genmodels instead.
	private static final String SCT_FILE_EXTENSION = "sct";
	private static final String SGEN_FILE_EXTENSION = "sgen";
	public static final String BUILDER_ID = "org.yakindu.sct.builder.SCTBuilder";
	private DefaultResourceBlacklist sgenBlackList;

	private final class ElementRefGenerator implements Predicate<GeneratorEntry> {

		private final URI uri;

		private ElementRefGenerator(EObject eobject) {
			if (EcoreUtil.getURI(eobject) != null) {
				uri = EcoreUtil.getURI(eobject);
			} else {
				uri = null;
			}
		}

		public boolean apply(GeneratorEntry input) {
			return uri != null && input.getElementRef() != null && !input.getElementRef().eIsProxy()
					&& uri.equals(EcoreUtil.getURI(input.getElementRef()));
		}
	}

	class DeltaVisitor implements IResourceDeltaVisitor {
		private Set<IResource> buildSgens = Sets.newHashSet();

		public boolean visit(IResourceDelta delta) throws CoreException {
			IResource resource = delta.getResource();
			switch (delta.getKind()) {
			case IResourceDelta.ADDED:
				// handle added resource
				doIt(resource, buildSgens);
				break;
			case IResourceDelta.REMOVED:
				// handle removed resource
				break;
			case IResourceDelta.CHANGED:
				// handle changed resource
				doIt(resource, buildSgens);
				break;
			}
			// return true to continue visiting children.
			return true;
		}
	}

	class SimpleResourceVisitor implements IResourceVisitor {
		private Set<IResource> buildSgens = Sets.newHashSet();

		public boolean visit(IResource resource) {
			doIt(resource, buildSgens);
			// return true to continue visiting children.
			return true;
		}
	}

	@Override
	protected IProject[] build(int kind, @SuppressWarnings("rawtypes") Map args, IProgressMonitor monitor)
			throws CoreException {

		IPreferenceStore store = BuilderActivator.getDefault().getPreferenceStore();
		boolean generateAutomatical = store.getBoolean(GeneratorActivator.PREF_GENERATE_AUTOMATICALLY);
		sgenBlackList = new DefaultResourceBlacklist();

		if (generateAutomatical) {
			if (kind == FULL_BUILD) {
				fullBuild(monitor);
			} else {
				IResourceDelta delta = getDelta(getProject());
				if (delta == null) {
					fullBuild(monitor);
				} else {
					incrementalBuild(delta, monitor);
				}
			}
		}
		return null;
	}

	protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
		try {
			getProject().accept(new SimpleResourceVisitor());
		} catch (CoreException e) {
		}
	}

	protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException {
		delta.accept(new DeltaVisitor());
	}

	/**
	 * Build the Statecharts inside this sgen-file or find all sgen-files for
	 * the statechart in the resource and build them.
	 * 
	 * @param changedResource
	 *            Resource to check, if it can be build.
	 * @param buildSgens
	 *            Contains a set of already build sgen files. Accepted
	 *            sgen-files are added inside this method.
	 */
	public void doIt(final IResource changedResource, final Set<IResource> buildSgens) {
		if (changedResource.getType() != IResource.FILE) {
			return;
		}
		if (SGEN_FILE_EXTENSION.equals(changedResource.getFileExtension()) && !buildSgens.contains(changedResource)) {
			if (hasError(changedResource)) {
				logGenmodelError(changedResource.getFullPath().toString());
			} else {
				buildSgens.add(changedResource);
				executeGenmodelGenerator(changedResource);
			}
		} else if (SCT_FILE_EXTENSION.equals(changedResource.getFileExtension())) {
			// TODO rely on indexed genmodel and referenced objects uri
			final Statechart statechart = loadFromResource(changedResource);
			if (statechart == null)
				return;
			try {
				changedResource.getProject().accept(new IResourceVisitor() {

					public boolean visit(IResource resource) throws CoreException {
						if (IResource.FILE == resource.getType()
								&& SGEN_FILE_EXTENSION.equals(resource.getFileExtension())
								&& !buildSgens.contains(resource) && isGenmodelForStatechart(resource, statechart)) {
							// TODO: would be good to filter the config for the
							// statechart so only the sct that changed is
							// build
							if (hasError(changedResource)) {
								logStatechartError(changedResource.getFullPath().toString());
							} else {
								if (hasError(resource)) {
									logGenmodelError(resource.getFullPath().toString());
								} else {
									buildSgens.add(resource);
									executeGenmodelGenerator(resource);
								}
							}
						}
						return true;
					}
				});
			} catch (CoreException e) {
				e.printStackTrace();
			}

		}
	}

	private boolean hasError(IResource resource) {
		IMarker[] findMarkers = null;
		try {
			findMarkers = resource.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
			for (IMarker iMarker : findMarkers) {
				Integer attribute = (Integer) iMarker.getAttribute(IMarker.SEVERITY);
				if (attribute.intValue() == IMarker.SEVERITY_ERROR) {
					return true;
				}
			}
		} catch (CoreException e) {
			e.printStackTrace();
		}
		return false;
	}

	protected void executeGenmodelGenerator(IResource resource) {
		if (sgenBlackList.violates(resource, resource.getProject())) {
			logGenmodelInfo(resource);
			return;
		}
		new EclipseContextGeneratorExecutorLookup()
				.executeGenerator(resource.getProject().getFile(resource.getProjectRelativePath()));
	}

	protected void logGenmodelInfo(IResource resource) {
		Status status = new Status(Status.INFO, BUILDER_ID,
				String.format(
						"Cannot execute Genmodel %s. The file is excluded from build. (see project properties > YAKINDU SCT > SGen Filter)",
						resource));
		Platform.getLog(BuilderActivator.getDefault().getBundle()).log(status);
	}

	protected void logGenmodelError(String resource) {
		Status status = new Status(Status.WARNING, BUILDER_ID,
				String.format("Cannot execute Genmodel %s. The file contains errors.", resource));
		Platform.getLog(BuilderActivator.getDefault().getBundle()).log(status);
	}

	protected void logStatechartError(final String resource) {
		Status status = new Status(Status.WARNING, BUILDER_ID,
				String.format("Cannot generate Code for Statechart %s. The file contains errors.", resource));
		Platform.getLog(BuilderActivator.getDefault().getBundle()).log(status);
	}

	private boolean isGenmodelForStatechart(IResource genmodelResource, final Statechart statechart) {
		GeneratorModel genModel = loadFromResource(genmodelResource);
		return genModel != null && !genModel.getEntries().isEmpty()
				&& Iterables.any(genModel.getEntries(), new ElementRefGenerator(statechart));
	}

	@SuppressWarnings("unchecked")
	private <TYPE extends EObject> TYPE loadFromResource(IResource res) {
		URI uri = URI.createPlatformResourceURI(res.getFullPath().toString(), true);
		ResourceSet set = new ResourceSetImpl();
		Resource emfResource = null;
		try {
			emfResource = set.getResource(uri, true);
		} catch (WrappedException e) {
			Platform.getLog(BuilderActivator.getDefault().getBundle()).log(new Status(IStatus.WARNING,
					GeneratorActivator.PLUGIN_ID, "Resource " + uri + " can not be loaded by builder", e));
			return null;
		}
		if (emfResource.getErrors().size() > 0 || emfResource.getContents().size() == 0)
			return null;
		return (TYPE) emfResource.getContents().get(0);
	}

}
